/* Bugzilla C# Proxy Library
   Copyright (C) 2006, Dansk BiblioteksCenter A/S
   Mads Bondo Dydensborg, <mbd@dbc.dk>
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   
   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/   
/*! \file
  \brief This is a very simple class, that parses command line arguments 

*/

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

// The idea is to have a class that can "parse" options, in a general
// sense. Individual options are then parsed by "handlers", which uses
// delegates to set (or do something else) variables based on the
// option read/its value. This is just a quick hack, as I would
// normallo use a library for something like this, but I do not want
// to make bugzproxy depend on an external library (other than the
// XML-RPC one). Documentation and error handling is lacking.

// For standalone (test), compile as:
// gmcs /warn:4 -d:TEST CommonArgs.cs


////////////////////////////////////////////////////////////////////////////////

// Delegates used by the handlers of options.
public delegate void SetFlag( string optionIndicator );
public delegate void SetArg<T>( T value ); 

// Interface that handlers of options must implement.
public interface IHandler {
  /// <summary>Handle reading an optionIndicator, with possible arguments</summary>
  /// <param name="optionIndicator">The optionIndicator name, such as "host" (from --host)</param>
  /// <param name="args">The arguments array</param>
  /// <param name="index">The index into args</param>
  /// <returns>How much to increase index for the next consumer</returns>
  int Handle( string optionIndicator, string[] args, int index );
}

// Handle a flag - there or not there
public class HandleFlag : IHandler {
  SetFlag setFlag;
  public HandleFlag( SetFlag setFlag ) {
    this.setFlag = setFlag;
  }
  
  public int Handle( string optionIndicator, string[] args, int index ) {
    setFlag( optionIndicator );
    return 0;
  }
} 


// Handle a single string value after an option
public class HandleString : IHandler {
  SetArg<string> setArg;
  public HandleString( SetArg<string> setArg ) {
    this.setArg = setArg;
  }
  public int Handle( string optionIndicator, string[] args, int index ) {
    if ( index >= args.Length ) {
      throw new ArgumentException( String.Format( "Missing argument for option {0}", 
                                                  optionIndicator ),
                                   "args" );
    }
    setArg( args[index] );
    return 1;
  }
}

// Handle a single int value after an option
public class HandleUInt : IHandler {
  SetArg<uint> setArg;
  public HandleUInt( SetArg<uint> setArg ) {
    this.setArg = setArg;
  }
  public int Handle( string optionIndicator, string[] args, int index ) {
    if ( index >= args.Length ) {
      throw new ArgumentException( String.Format( "Missing argument for option {0}", 
                                                  optionIndicator ),
                                   "args" );
    }
    setArg( uint.Parse( args[index] ) );
    return 1;
  }
}


////////////////////////////////////////////////////////////////////////////////
// The class that does the actual "parsing" of the arguments.
// Support "/" and "-" and "--" as option indicators.
public class ArgParser {

  /// <summary>Store handlers, keyed on the option name, here.</summary>
  Dictionary<string, IHandler> handlers;

  public ArgParser() {
    handlers = new Dictionary<string, IHandler>();
  }
  
  /// <summary>Return true if string starts with option indicator</summary>
  bool IsOptionIndicator( string arg ) {
    return ( arg != null ) && ( arg.Length != 0 ) && ( arg[0] == '/' || arg[0] == '-' );
  }
  
  /// <summary>Removes prefix /,-,-- from string</summary>
  string GetOptionIndicator( string arg ) {
    return Regex.Replace( arg, @"^[/-][-]?", "" );
  }
  
  /// <summary>Add a handler</summary> <remarks>Note, adding multiple
  /// with the same key throws. There is no way to remove a
  /// handler</remarks>
  /// <param name="optionIndicator">The main name for the option, such as "help"</param>
  /// <param name="alt">Alternate name for the option, such as "?"</param>
  /// <param name="handler">The actual handler for the option</param>
  public void Add( string optionIndicator, string alt, IHandler handler ) {
    handlers.Add( optionIndicator, handler );
    if ( alt != "" ) {
      handlers.Add( alt, handler );
    }
  }

  /// <summary>Parse arguments, call delegates for options, returns
  /// array of not matched arguments</summary>
  public List<string> Parse( string[] args ) {
    int index = 0;
    List<string> res = new List<string>();
    while( index < args.Length ) {
      if ( IsOptionIndicator( args[index] ) ) {
        string optionIndicator = GetOptionIndicator( args[index] );
        // Locate handler, go on, if handler not found.
        IHandler handler;
        try {
          handler = handlers[ optionIndicator ];
        } 
        catch {
          res.Add( args[index] );
          ++index;
          continue;
        }
        // Has a handler, try it outside catch
        ++index;
        index += handler.Handle( optionIndicator, args, index );
      } else {
        res.Add( args[index] );
        ++index;
      }
    }
    return res;
  }

} // ArgParser

////////////////////////////////////////////////////////////////////////////////
// Args common to all example programs. Or should be, at least.
// Inherit from this, and add own args, in example programs.

public class CommonArgs : ArgParser {
  // Common arguments

  // Help and trace flags
  public bool Help;
  public bool Trace;
  
  // Host and user information
  public string Host;
  public string Path;
  public uint Port;
  public string User;
  public string Password;

  // For use when describing options.
  public string Summary() {
    return "[--help|--?] [--t(race)] [--h(ost) <hostname>] [--path <path>] [--port <portnumber>] [--u(ser) <user>] [--p(assword) <password>]";
  }
  
  // For debug mostly. Assumes 8 char tabs.
  public string Values() {
    return String.Format( "\ttrace\t\t = {0}\n\thost\t\t = {1}\n\tpath\t\t = {2}\n\tport\t\t = {3}\n\tuser\t\t = {4}\n\tpassword\t = {5}", 
                          Trace, Host, Path, Port, User, Password );
  }

  public CommonArgs() : base() {
    // Set default values.
    Help  = false;
    Trace = false;
    Host     = "landfill.bugzilla.org";
    Path     = "bugzilla-tip";
    Port     = 80;
    User     = "mbd@dbc.dk";
    Password = "bugzproxy";

    // Add default handlers
    Add( "help",     "?", new HandleFlag( delegate( string ignored ) { Help = true; } ) );
    Add( "trace",    "t", new HandleFlag( delegate( string ignored ) { Trace = true; } ) );
    Add( "host",     "h", new HandleString( delegate( string value ) { Host = value; } ) );
    Add( "path",     "",  new HandleString( delegate( string value ) { Path = value; } ) );
    Add( "port",     "",  new HandleUInt( delegate( uint value )     { Port = value; } ) );
    Add( "user",     "u", new HandleString( delegate( string value ) { User = value; } ) );
    Add( "password", "p", new HandleString( delegate( string value ) { Password = value; } ) );
  }

} // CommonArgs

// And, this is for the apps, that does not really need to do any
// other arguments than the common ones
public class SimpleAppArgs : CommonArgs {
  string exeName;
  string description;
  public SimpleAppArgs( string exeName, string description ) : base() {
    this.exeName     = exeName;
    this.description = description;
  }
  private void Usage() {
    System.Console.WriteLine( description );
    System.Console.WriteLine( "Usage: {0} {1}", exeName, base.Summary() );
  }
  
  new public bool Parse( string[] args ) {
    List<string> residual = base.Parse( args );
    if ( residual.Count != 0 ) {
      System.Console.WriteLine( "Error in commandline: Unable to parse {0}", residual[0] );
      Usage();
      return false;
    }
    if ( Help ) {
      Usage();
      return false;
    }
    return true;
  }
} // SimpleAppArgs


////////////////////////////////////////////////////////////////////////////////
// Lazy me, commented test program

#if (TEST)
public class MyMain {

  string[] args;
 
  SimpleAppArgs argParser;

  public MyMain( string[] args ) {
    argParser = new SimpleAppArgs( "SimpleAppArgs.exe", 
                                   "Test program for the CommonArgs and related classes" );
    this.args = args;
  }

  public int Run() {
    if ( argParser.Parse( args ) ) {
      Console.WriteLine( "Settings: \n{0}", argParser.Values() );
      return 0;
    } else {
      return 1;
    }
  }

  public static int Main( string[] args ) {
    MyMain myMain = new MyMain( args );
    return myMain.Run();
  }
}
#endif


